From dcdcc659ef0b4640bd53e407f8bf880235c116b5 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Tue, 11 Jan 2022 21:35:19 -0500 Subject: [PATCH] directorylist: Fix several issues We were handling events in the wrong order, by doing async calls for some of them, but not for all of them. And we were not taking into account that GFileMonitors RENAMED events may or may not move a file on top of an existing file. Fixes: #3784 --- gtk/gtkdirectorylist.c | 238 +++++++++++++++++++++++++++++++---------- 1 file changed, 180 insertions(+), 58 deletions(-) diff --git a/gtk/gtkdirectorylist.c b/gtk/gtkdirectorylist.c index a164eac8ef..3b8908aefd 100644 --- a/gtk/gtkdirectorylist.c +++ b/gtk/gtkdirectorylist.c @@ -65,6 +65,25 @@ enum { NUM_PROPERTIES }; +typedef struct _QueuedEvent QueuedEvent; +struct _QueuedEvent +{ + GtkDirectoryList *list; + GFile *file; + GFileInfo *info; + GFileMonitorEvent event; +}; + +static void +free_queued_event (gpointer data) +{ + QueuedEvent *event = data; + + g_clear_object (&event->file); + g_clear_object (&event->info); + g_free (event); +} + struct _GtkDirectoryList { GObject parent_instance; @@ -78,6 +97,7 @@ struct _GtkDirectoryList GCancellable *cancellable; GError *error; /* Error while loading */ GSequence *items; /* Use GPtrArray or GListStore here? */ + GQueue events; }; struct _GtkDirectoryListClass @@ -140,7 +160,6 @@ gtk_directory_list_set_property (GObject *object, case PROP_ATTRIBUTES: gtk_directory_list_set_attributes (self, g_value_get_string (value)); break; - case PROP_FILE: gtk_directory_list_set_file (self, g_value_get_object (value)); break; @@ -238,6 +257,9 @@ gtk_directory_list_dispose (GObject *object) g_clear_error (&self->error); g_clear_pointer (&self->items, g_sequence_free); + g_queue_foreach (&self->events, (GFunc) free_queued_event, NULL); + g_queue_clear (&self->events); + G_OBJECT_CLASS (gtk_directory_list_parent_class)->dispose (object); } @@ -331,6 +353,7 @@ gtk_directory_list_init (GtkDirectoryList *self) self->items = g_sequence_new (g_object_unref); self->io_priority = G_PRIORITY_DEFAULT; self->monitored = TRUE; + g_queue_init (&self->events); } /** @@ -519,78 +542,136 @@ gtk_directory_list_start_loading (GtkDirectoryList *self) g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_LOADING]); } -static void -got_new_file_info_cb (GObject *source, - GAsyncResult *res, - gpointer data) +static GSequenceIter * +find_file (GSequence *sequence, + GFile *file) { - GFile *file = G_FILE (source); - GtkDirectoryList *self = GTK_DIRECTORY_LIST (data); - GFileInfo *info; - guint position; + GSequenceIter *iter; - info = g_file_query_info_finish (file, res, NULL); - if (!info) - return; + for (iter = g_sequence_get_begin_iter (sequence); + !g_sequence_iter_is_end (iter); + iter = g_sequence_iter_next (iter)) + { + GFileInfo *item = G_FILE_INFO (g_sequence_get (iter)); + GFile *f = G_FILE (g_file_info_get_attribute_object (item, "standard::file")); - g_file_info_set_attribute_object (info, "standard::file", G_OBJECT (file)); - position = g_sequence_get_length (self->items); - g_sequence_append (self->items, info); - g_list_model_items_changed (G_LIST_MODEL (self), position, 0, 1); + if (g_file_equal (f, file)) + return iter; + } + + return NULL; } -static void -got_existing_file_info_cb (GObject *source, - GAsyncResult *res, - gpointer data) +static gboolean +handle_event (QueuedEvent *event) { - GFile *file = G_FILE (source); - GtkDirectoryList *self = GTK_DIRECTORY_LIST (data); - GFileInfo *info; + GtkDirectoryList *self = event->list; + GFile *file = event->file; + GFileInfo *info = event->info; GSequenceIter *iter; + unsigned int position; - info = g_file_query_info_finish (file, res, NULL); - if (!info) - return; + switch ((int)event->event) + { + case G_FILE_MONITOR_EVENT_MOVED_IN: + case G_FILE_MONITOR_EVENT_CREATED: + if (!info) + return FALSE; - g_file_info_set_attribute_object (info, "standard::file", G_OBJECT (file)); + g_file_info_set_attribute_object (info, "standard::file", G_OBJECT (file)); - for (iter = g_sequence_get_begin_iter (self->items); - !g_sequence_iter_is_end (iter); - iter = g_sequence_iter_next (iter)) - { - GFileInfo *item = g_sequence_get (iter); - GFile *f = G_FILE (g_file_info_get_attribute_object (item, "standard::file")); - if (g_file_equal (f, file)) + iter = find_file (self->items, file); + if (iter) { - guint position = g_sequence_iter_get_position (iter); + position = g_sequence_iter_get_position (iter); g_sequence_set (iter, g_object_ref (info)); g_list_model_items_changed (G_LIST_MODEL (self), position, 1, 1); - break; } + else + { + position = g_sequence_get_length (self->items); + g_sequence_append (self->items, g_object_ref (info)); + g_list_model_items_changed (G_LIST_MODEL (self), position, 0, 1); + } + break; + + case G_FILE_MONITOR_EVENT_MOVED_OUT: + case G_FILE_MONITOR_EVENT_DELETED: + iter = find_file (self->items, file); + if (iter) + { + position = g_sequence_iter_get_position (iter); + g_sequence_remove (iter); + g_list_model_items_changed (G_LIST_MODEL (self), position, 1, 0); + } + break; + + case G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED: + if (!info) + return FALSE; + + g_file_info_set_attribute_object (info, "standard::file", G_OBJECT (file)); + + iter = find_file (self->items, file); + if (iter) + { + position = g_sequence_iter_get_position (iter); + g_sequence_set (iter, g_object_ref (info)); + g_list_model_items_changed (G_LIST_MODEL (self), position, 1, 1); + } + break; + + default: + g_assert_not_reached (); } + + return TRUE; } static void -gtk_directory_list_remove_file (GtkDirectoryList *self, - GFile *file) +handle_events (GtkDirectoryList *self) { - GSequenceIter *iter; + QueuedEvent *event; - for (iter = g_sequence_get_begin_iter (self->items); - !g_sequence_iter_is_end (iter); - iter = g_sequence_iter_next (iter)) + do { - GFileInfo *item = g_sequence_get (iter); - GFile *f = G_FILE (g_file_info_get_attribute_object (item, "standard::file")); - if (g_file_equal (f, file)) - { - guint position = g_sequence_iter_get_position (iter); - g_sequence_remove (iter); - g_list_model_items_changed (G_LIST_MODEL (self), position, 1, 0); - break; - } + event = g_queue_peek_tail (&self->events); + if (!event) + return; + + if (!handle_event (event)) + return; + + event = g_queue_pop_tail (&self->events); + free_queued_event (event); } + while (TRUE); +} + +static void +got_new_file_info_cb (GObject *source, + GAsyncResult *res, + gpointer data) +{ + QueuedEvent *event = data; + GtkDirectoryList *self = event->list; + GFile *file = event->file; + + event->info = g_file_query_info_finish (file, res, NULL); + handle_events (self); +} + +static void +got_existing_file_info_cb (GObject *source, + GAsyncResult *res, + gpointer data) +{ + QueuedEvent *event = data; + GtkDirectoryList *self = event->list; + GFile *file = event->file; + + event->info = g_file_query_info_finish (file, res, NULL); + handle_events (self); } static void @@ -601,30 +682,74 @@ directory_changed (GFileMonitor *monitor, gpointer data) { GtkDirectoryList *self = GTK_DIRECTORY_LIST (data); + QueuedEvent *ev; + switch (event) { + case G_FILE_MONITOR_EVENT_MOVED_IN: case G_FILE_MONITOR_EVENT_CREATED: + ev = g_new0 (QueuedEvent, 1); + ev->list = self; + ev->event = event; + ev->file = g_object_ref (file); + g_queue_push_head (&self->events, ev); + g_file_query_info_async (file, self->attributes, G_FILE_QUERY_INFO_NONE, self->io_priority, self->cancellable, got_new_file_info_cb, - self); + ev); break; + case G_FILE_MONITOR_EVENT_MOVED_OUT: case G_FILE_MONITOR_EVENT_DELETED: - gtk_directory_list_remove_file (self, file); + ev = g_new0 (QueuedEvent, 1); + ev->list = self; + ev->event = event; + ev->file = g_object_ref (file); + g_queue_push_head (&self->events, ev); + + handle_events (self); break; case G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED: + ev = g_new0 (QueuedEvent, 1); + ev->list = self; + ev->event = event; + ev->file = g_object_ref (file); + g_queue_push_head (&self->events, ev); + g_file_query_info_async (file, self->attributes, G_FILE_QUERY_INFO_NONE, self->io_priority, self->cancellable, got_existing_file_info_cb, - self); + ev); + break; + + case G_FILE_MONITOR_EVENT_RENAMED: + ev = g_new0 (QueuedEvent, 1); + ev->list = self; + ev->event = G_FILE_MONITOR_EVENT_DELETED; + ev->file = g_object_ref (file); + g_queue_push_head (&self->events, ev); + + ev = g_new0 (QueuedEvent, 1); + ev->list = self; + ev->event = G_FILE_MONITOR_EVENT_CREATED; + ev->file = g_object_ref (other_file); + g_queue_push_head (&self->events, ev); + + g_file_query_info_async (other_file, + self->attributes, + G_FILE_QUERY_INFO_NONE, + self->io_priority, + self->cancellable, + got_existing_file_info_cb, + ev); break; case G_FILE_MONITOR_EVENT_CHANGED: @@ -632,9 +757,6 @@ directory_changed (GFileMonitor *monitor, case G_FILE_MONITOR_EVENT_PRE_UNMOUNT: case G_FILE_MONITOR_EVENT_UNMOUNTED: case G_FILE_MONITOR_EVENT_MOVED: - case G_FILE_MONITOR_EVENT_RENAMED: - case G_FILE_MONITOR_EVENT_MOVED_IN: - case G_FILE_MONITOR_EVENT_MOVED_OUT: default: break; } @@ -644,7 +766,7 @@ static void gtk_directory_list_start_monitoring (GtkDirectoryList *self) { g_assert (self->monitor == NULL); - self->monitor = g_file_monitor_directory (self->file, G_FILE_MONITOR_NONE, NULL, NULL); + self->monitor = g_file_monitor_directory (self->file, G_FILE_MONITOR_WATCH_MOVES, NULL, NULL); g_signal_connect (self->monitor, "changed", G_CALLBACK (directory_changed), self); } -- 2.30.2